﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using System.Web;

using Microsoft.Practices.ServiceLocation;

using Happy.ExtentionMethods;
using Happy.ExtentionMethods.Reflection;

namespace Happy.Bootstrap.Internal
{
    internal sealed class DefaultBootstrapService : IBootstrapService
    {
        private bool _isStarted = false;
        private readonly List<Predicate<Assembly>> _filters = new List<Predicate<Assembly>>();
        private readonly List<IBootstrapPlugin> _plugins = new List<IBootstrapPlugin>();
        private IServiceLocator _serviceLocator;

        public IServiceLocator ServiceLocator
        {
            get { return _serviceLocator; }
        }


        public IBootstrapService Include(Predicate<Assembly> filter)
        {
            filter.MustNotNull("filter");
            this.MustNotStarted();

            _filters.Add(filter);

            return this;
        }

        public IBootstrapService AddPlugin(IBootstrapPlugin plugin)
        {
            plugin.MustNotNull("plugin");
            this.MustNotStarted();

            _plugins.Add(plugin);

            return this;
        }

        public IBootstrapService UseServiceLocator(IServiceLocator serviceLocator)
        {
            serviceLocator.MustNotNull("serviceLocator");
            this.MustNotStarted();

            _serviceLocator = serviceLocator;

            Microsoft.Practices.ServiceLocation.ServiceLocator.SetLocatorProvider(() => _serviceLocator);

            return this;
        }

        public void Start()
        {
            this.MustNotStarted();

            this.LoadAssemblies();

            var assemblies = this.GetParticipateInBootstrapAssemblies();

            foreach (var assembly in assemblies)
            {
                this.ExecutePlugs(assembly);
            }

            foreach (var assembly in assemblies)
            {
                this.ExecuteListeners(assembly);
            }
        }

        private void LoadAssemblies()
        {
            var applicationAssemblyDirectory = GetApplicationAssemblyDirectory();

            foreach (var file in Directory.GetFiles(applicationAssemblyDirectory, "*.dll", SearchOption.TopDirectoryOnly))
            {
                var assemblyName = AssemblyName.GetAssemblyName(file);
                AppDomain.CurrentDomain.Load(assemblyName);
            }
        }

        private List<Assembly> GetParticipateInBootstrapAssemblies()
        {
            return AppDomain
                .CurrentDomain
                .GetAssemblies()
                .Where(this.CanParticipateInBootstrap)
                .ToList();
        }

        private bool CanParticipateInBootstrap(Assembly assembly)
        {
            return
                assembly.IsDefined(typeof(ParticipateInBootstrapAttribute), false)
                ||
                _filters.Any(f => f(assembly));
        }

        private void ExecuteListeners(Assembly assembly)
        {
            var listeners = assembly.CreateConcreteDescendentInstances<IBootstrapListener>();

            foreach (var listener in listeners)
            {
                listener.Start(this);
            }
        }

        private void ExecutePlugs(Assembly assembly)
        {
            foreach (var plug in _plugins)
            {
                plug.Start(this, assembly);
            }
        }

        private void MustNotStarted()
        {
            if (_isStarted)
            {
                throw new InvalidOperationException(Resource.Messages.Error_BootstrapServiceMustNotStarted);
            }
        }

        private static string GetApplicationAssemblyDirectory()
        {
            var directory = AppDomain.CurrentDomain.BaseDirectory;

            if (HttpContext.Current == null)
            {
                return directory;
            }

            return Path.Combine(directory, "Bin");
        }
    }
}
